package redis

import (
	"encoding/binary"
	"gitee.com/Cauchy_AQ/gokv"
)

// ================================= List 结构 =========================== //

func (r *RedisDataStruct) LPush(key, element []byte) (uint32, error) {
	return r.pushInner(key, element, true)
}

func (r *RedisDataStruct) RPush(key, element []byte) (uint32, error) {
	return r.pushInner(key, element, false)
}

func (r *RedisDataStruct) LPop(key []byte) ([]byte, error) {
	return r.popInner(key, true)
}

func (r *RedisDataStruct) RPop(key []byte) ([]byte, error) {
	return r.popInner(key, false)
}

func (r *RedisDataStruct) LLen(key []byte) (uint32, error) {
	meta, exist, err := r.findMetadata(key, List)
	if err != nil {
		return 0, err
	}
	if !exist {
		return 0, nil
	}
	return meta.size, nil
}

func (r *RedisDataStruct) LIndex(key []byte, index int32) ([]byte, error) {
	meta, exist, err := r.findMetadata(key, List)
	if err != nil {
		return nil, err
	}
	if !exist {
		return nil, nil
	}
	ok, idx := checkAndConvertIndex2LIndex(int32(meta.size), index)
	if !ok {
		return nil, nil
	}

	internalKey := listInternalKey{
		key:     key,
		version: meta.version,
		index:   meta.head + uint64(idx),
	}
	encKey := internalKey.encode()

	return r.db.Get(encKey)
}

func (r *RedisDataStruct) LRange(key []byte, start, stop int32) ([][]byte, error) {
	meta, exist, err := r.findMetadata(key, List)
	if err != nil {
		return nil, err
	}
	if !exist {
		return nil, nil
	}

	size := int32(meta.size)
	_, lIndex := checkAndConvertIndex2LIndex(size, start)
	_, rIndex := checkAndConvertIndex2LIndex(size, stop)

	res := make([][]byte, 0)
	for i := lIndex; i <= rIndex; i++ {
		internalKey := listInternalKey{
			key:     key,
			version: meta.version,
			index:   meta.head + uint64(i),
		}
		encKey := internalKey.encode()

		if val, err := r.db.Get(encKey); err == nil {
			res = append(res, val)
		} else {
			break
		}
	}
	return res, nil
}

// checkAndConvertIndex2LIndex 检查 index 是否越界，并将 index 转为从左定位的 index 值
func checkAndConvertIndex2LIndex(size, index int32) (bool, uint32) {
	if index < 0 {
		index = size + index
	}
	var isOverFlow bool = false
	if index >= 0 && index >= size || index < 0 {
		isOverFlow = true
	}

	if index < 0 {
		index = 0
	}

	return !isOverFlow, uint32(index)
}

// pushInner 实际 push 数据
// head 是实际数据的第一位，tail 是实际数据最后一位的下一位
func (r *RedisDataStruct) pushInner(key, element []byte, isLeft bool) (uint32, error) {
	meta, err := r.findAndCreateMetadata(key, List)
	if err != nil {
		return 0, err
	}

	internalKey := listInternalKey{
		key:     key,
		version: meta.version,
	}
	if isLeft {
		internalKey.index = meta.head - 1
		meta.head -= 1
	} else {
		internalKey.index = meta.tail
		meta.tail += 1
	}
	meta.size += 1

	writeBatchOpts := gokv.DefaultWriteBatchOptions
	wb := r.db.NewWriteBatch(&writeBatchOpts)
	_ = wb.Put(key, meta.encode())
	_ = wb.Put(internalKey.encode(), element)
	if err := wb.Commit(); err != nil {
		return 0, err
	}
	return meta.size, nil
}

func (r *RedisDataStruct) popInner(key []byte, isLeft bool) ([]byte, error) {
	meta, exist, err := r.findMetadata(key, List)
	if err != nil {
		return nil, err
	}
	if !exist {
		return nil, nil
	}

	internalKey := listInternalKey{
		key:     key,
		version: meta.version,
	}
	if isLeft {
		internalKey.index = meta.head
		meta.head += 1
	} else {
		internalKey.index = meta.tail - 1
		meta.tail -= 1
	}
	meta.size -= 1

	element, err := r.db.Get(internalKey.encode())
	if err != nil {
		return nil, err
	}

	writeBatchOpts := gokv.DefaultWriteBatchOptions
	wb := r.db.NewWriteBatch(&writeBatchOpts)
	_ = wb.Put(key, meta.encode())
	_ = wb.Delete(internalKey.encode())

	if meta.size == 0 {
		_ = wb.Delete(key)
	}
	if err := wb.Commit(); err != nil {
		return nil, err
	}
	return element, nil
}

// listInternalKey list 结构 key 值
type listInternalKey struct {
	key     []byte
	version int64
	index   uint64
}

// encode 编码
// key|version|index -> value
func (l *listInternalKey) encode() []byte {
	lenk := len(l.key)
	buf := make([]byte, lenk+8+8)
	copy(buf[:lenk], l.key)
	binary.LittleEndian.PutUint64(buf[lenk:], uint64(l.version))
	binary.LittleEndian.PutUint64(buf[lenk+8:], uint64(l.index))
	return buf
}
